package utils;

public class ArrayList {
   private Object[] storedObjects;
   private int growthFactor;
   private int size;
   
   /**
    * Creates an ArrayList with the initial capacity of 10 and a growth factor of 75%
    */
   public ArrayList() {
      this( 10, 75 );
   }
   
   /**
    * creates an ArrayList with the given initial capacity and a growth factor of 75%
    * 
    * @param initialCapacity the capacity of this array list.
    */
   public ArrayList( int initialCapacity ) {
      this( initialCapacity, 75 );
   }

   /**
    * Creates a new ArrayList
    * 
    * @param initialCapacity the capacity of this array list.
    * @param growthFactor the factor in % for increasing the capacity 
    *                           when there's not enough room in this list anymore 
    */
   public ArrayList( int initialCapacity, int growthFactor ) {
      this.storedObjects = new Object[ initialCapacity ];
      this.growthFactor = growthFactor;
   }
   
   /**
    * Retrieves the current size of this array list.
    *  
    * @return the number of stored elements in this list.
    */
   public int size() {
      return this.size;
   }
   
   /**
    * Determines whether the given element is stored in this list.
    * 
    * @param element the element which might be stored in this list
    * @return true when the given element is stored in this list
    * @throws IllegalArgumentException when the given element is null
    * @see #remove(Object)
    */
   public boolean contains( Object element ) {
      if (element == null) {
         throw new IllegalArgumentException( "ArrayList cannot contain a null element.");
      }
      for (int i = 0; i < this.size; i++) {
         Object object = this.storedObjects[i];
         if ( object.equals( element ) ) {
            return true;
         }
      }
      return false;
   }
   
   /**
    * Retrieves the index of the given object.
    * 
    * @param element the object which is part of this list.
    * @return the index of the object or -1 when the object is not part of this list.
    * @throws IllegalArgumentException when the given element is null
    */
   public int indexOf(Object element) {
      if (element == null) {
         throw new IllegalArgumentException( "ArrayList cannot contain a null element.");
      }
      for (int i = 0; i < this.size; i++) {
         Object object = this.storedObjects[i];
         if ( object.equals( element ) ) {
            return i;
         }
      }
      return -1;
   }
   
   /**
    * Returns the element at the specified position in this list.
    *  
    * @param index the position of the desired element.
    * @return the element stored at the given position
    * @throws IndexOutOfBoundsException when the index < 0 || index >= size()
    */
   public Object get( int index ) {
      if (index < 0 || index >= this.size ) {
         throw new IndexOutOfBoundsException("the index [" + index + "] is not valid for this list with the size [" + this.size + "].");
      }
      return this.storedObjects[ index ];
   }
      
   /**
    * Removes the element at the specified position in this list.
    *  
    * @param index the position of the desired element.
    * @return the element stored at the given position
    * @throws IndexOutOfBoundsException when the index < 0 || index >= size()
    */
   public Object remove( int index ) {
      if (index < 0 || index >= this.size ) {
         throw new IndexOutOfBoundsException("the index [" + index + "] is not valid for this list with the size [" + this.size + "].");
      }
      Object removed = this.storedObjects[ index ];
      for (int i = index+1; i < this.size; i++) {
         this.storedObjects[ i-1 ] = this.storedObjects[ i ];
      }
      this.size--;
      return removed; 
   }
   
   /**
    * Removes the given element.
    * 
    * @param element the element which should be removed.
    * @return true when the element was found in this list.
    * @throws IllegalArgumentException when the given element is null
    * @see #contains(Object)
    */
   public boolean remove( Object element ) {
      if (element == null) {
         throw new IllegalArgumentException( "ArrayList cannot contain null.");
      }
      int index = -1;
      for (int i = 0; i < this.size; i++) {
         Object object = this.storedObjects[i];
         if ( object.equals( element ) ) {
            index = i;
            break;
         }
      }
      if (index == -1) {
         return false;
      }
      for (int i = index+1; i < this.size; i++) {
         this.storedObjects[ i-1 ] = this.storedObjects[ i ];
      }
      this.size--;
      return true; 
   }
   
   /**
    * Removes all of the elements from this list. 
    * The list will be empty after this call returns. 
    */
   public void clear() {
      for (int i = 0; i < this.size; i++) {
         this.storedObjects[i] = null;
      }
      this.size = 0;
   }

   /**
    * Stores the given element in this list.
    * 
    * @param element the element which should be appended to this list.
    * @throws IllegalArgumentException when the given element is null
    * @see #add( int, Object )
    */
   public void add( Object element) {
      if (element == null) {
         throw new IllegalArgumentException( "ArrayList cannot contain null.");
      }
      if (this.size >= this.storedObjects.length) {
         increaseCapacity();
      }
      this.storedObjects[ this.size ] = element;
      this.size++;
   }
   
   /**
    * Inserts the given element at the defined position.
    * Any following elements are shifted one position to the back.
    * 
    * @param index the position at which the element should be inserted, 
    *                 use 0 when the element should be inserted in the front of this list.
    * @param element the element which should be inserted
    * @throws IllegalArgumentException when the given element is null
    * @throws IndexOutOfBoundsException when the index < 0 || index >= size()
    */
   public void add( int index, Object element ) {
      if (element == null) {
         throw new IllegalArgumentException( "ArrayList cannot contain null.");
      }
      if (index < 0 || index >= this.size ) {
         throw new IndexOutOfBoundsException("the index [" + index + "] is not valid for this list with the size [" + this.size + "].");
      }
      if (this.size >= this.storedObjects.length) {
         increaseCapacity();
      }
      // shift all following elements one position to the back:
      for (int i = this.size; i > index; i--) {
         this.storedObjects[i] = this.storedObjects[ i-1 ];
      } 
      // insert the given element:
      this.storedObjects[ index ] = element;
      this.size++;
   }
   
   /**
    * Replaces the element at the specified position in this list with the specified element. 
    * 
    * @param index the position of the element, the first element has the index 0.
    * @param element the element which should be set
    * @return the replaced element
    * @throws IndexOutOfBoundsException when the index < 0 || index >= size()
    */
   public Object set( int index, Object element ) {
      if (index < 0 || index >= this.size ) {
         throw new IndexOutOfBoundsException("the index [" + index + "] is not valid for this list with the size [" + this.size + "].");
      }
      Object replaced = this.storedObjects[ index ];
      this.storedObjects[ index ] = element;
      return replaced;
   }
   
   /**
    * Returns all stored elements as an array.
    * 
    * @return the stored elements as an array.
    */
   public Object[] toArray() {
      Object[] copy = new Object[ this.size ];
      System.arraycopy( this.storedObjects, 0, copy, 0, this.size );
      return copy;
   }
   
   /**
    * Returns all stored elements in the given array.
    * 
    * @param target the array in which the stored elements should be copied.
    * @return the stored elements of this list
    */
   public Object[] toArray( Object[] target ) {
      System.arraycopy( this.storedObjects, 0, target, 0, this.size );
      return target;
   }
   
   /**
    * Trims the capacity of this ArrayList instance to be the list's current size. 
    * An application can use this operation to minimize the storage of an ArrayList instance.
    */
   public void trimToSize() {
      if (this.storedObjects.length != this.size ) {
         Object[] newStore = new Object[ this.size ];
         System.arraycopy( this.storedObjects, 0, newStore, 0, this.size );
         this.storedObjects = newStore;
      }
   }
   

   /**
    * increases the capacity of this list.
    */
   private void increaseCapacity() {
      int currentCapacity = this.storedObjects.length;
      int newCapacity = currentCapacity + ((currentCapacity * this.growthFactor) / 100);
      if (newCapacity == currentCapacity ) {
         newCapacity++;
      }
      Object[] newStore = new Object[ newCapacity ];
      System.arraycopy( this.storedObjects, 0, newStore, 0, this.size );
      this.storedObjects = newStore;
   }
}
